#pragma once
#include "elements_test.h"
#include "dvo.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>

//pushflow elements
static void test_ps_receiver();
static void test_ps_transmitter();
static void test_ps_transceiver();
static void test_ps_joiner();
static void test_ps_detacher();
static void test_ps_selector();
static void test_ps_restrictor();
static void test_ps_separator();


//pullflow elements
static void test_pl_receiver();
static void test_pl_transmitter();
static void test_pl_transceiver();
static void test_pl_joiner();
static void test_pl_detacher();
static void test_pl_selector();
static void test_pl_restrictor();
static void test_pl_separator();

//adapters elements
static void test_accumulator();
static void test_relayer();
static void test_provider();
static void test_pl_ref_transceiver();
static void test_ps_ref_transceiver();


void elements_test()
{
	test_ps_receiver();
	test_ps_transmitter();
    test_ps_transceiver();
	test_ps_joiner();
	test_ps_detacher();
	test_ps_selector();
	test_ps_restrictor();
	test_ps_separator();

	std::cout << std::endl;
   
	test_pl_receiver();
	test_pl_transmitter();
    test_pl_transceiver();
	test_pl_joiner();
	test_pl_detacher();
	test_pl_selector();
	test_pl_restrictor();
	test_pl_separator();

	std::cout << std::endl;

	test_accumulator();
	test_relayer();
    test_provider();
    test_pl_ref_transceiver();
    test_ps_ref_transceiver();
    
	std::cout << std::endl;
}

void test_ps_receiver()
{
	//initiate the result and the value variables
	int something = 0;
	int *value = new int(1), *result = &something;
	
	//initiate processing elements
	dvo::ps::SimpleReceiver<int> tester = [&](int& value) {result = &++value; }; 
	
	//send the value into the network
	*value >> tester.Input(); 

	//test the result
	bool test_failed = *result != 2 || result != value;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Receiver" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_ps_transmitter()
{
	//initiate the result and the value variables
	int something = 0;
	int *value = new int(1), *result = &something;

	//initiate processing elements
	dvo::ps::Socket<int> ps_socket = [&](int& value) {result = &++value; };
	dvo::ps::SimpleTransmitter<int> tester = [&]()->int& {return *value; };
	
	//define connections
	tester.Output() >> ps_socket;

	//test the result
	tester.Process();
	bool test_failed = *result != 2 || result != value;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Transmitter" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}

void test_ps_transceiver()
{
	//initiate the result and the value variables
	int something = 0;
	int *value = new int(1), *result = &something;

	//initiate processing elements
	dvo::ps::Socket<int> ps_socket = [&](int& v) {result = &v; };
	dvo::ps::SimpleTransceiver<int, int> tester = [&](int& value)->int& { return ++value; };
	
	//define connections
	tester.Output() >> ps_socket;

	//send the value into the network
	*value >> tester.Input();

	//test the result
	bool test_failed = *result != 2 || result != value;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Transceiver" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_ps_joiner()
{
    //initiate the result and the value variables
    std::promise<void> ready;  
	int *result, *sum = new int(0), *v1 = new int(1), *v2 = new int(1);

    //initiate processing elements
	dvo::ps::Socket<int> ps_socket = [&](int& fwd) { result = &fwd; ready.set_value(); };
    dvo::ps::SimpleJoiner<int, int, int> tester(1, 2);
	tester = [&](const std::map<int, int*>& map)->int& { return *sum = *map.at(1) + *map.at(2); };
	
    //define connections
	tester.Output() >> ps_socket;
    
    //send the value into the network
	auto fd1 = std::async( std::launch::async, [&]() { *v1 >> tester.Inputs(1); } );
	auto fd2 = std::async( std::launch::async, [&]() { *v2 >> tester.Inputs(2); } );
	
    //test the result  
	ready.get_future().wait();
	bool test_failed = *sum != 2 || result != sum;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Joiner" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_ps_detacher()
{
    //initiate the result and the value variables
	std::promise<void> signal1, signal2;
	int *source = new int(1), *v1, *v2;
    
    //initiate processing elements
	dvo::ps::Socket<int> ps_socket  = [&](int& v) {v1 = &v; signal1.set_value(); };
	dvo::ps::Socket<int> ps_socket2 = [&](int& v) {v2 = &v; signal2.set_value(); };
	dvo::ps::SimpleDetacher<int, int, int> tester(1, 2);
	tester = [&](int& v)->const std::map < int, int& >
	{
		std::map<int, int&> map; 
		++v;
		map.emplace(1, v);
		map.emplace(2, v);
		return map; 
	};

     //define connections
	tester.Outputs(1) >> ps_socket;
	tester.Outputs(2) >> ps_socket2;

     //send the value into the network
	*source >> tester.Input();

    //test the result
	signal1.get_future().wait(); signal2.get_future().wait();
    bool test_failed = v1 != source || v2 != source;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Detacher" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_ps_selector()
{
	 //initiate the result and the value variables
	int *source = new int(1), *result1 = nullptr, *result2 = nullptr;
    
    //initiate processing elements
    dvo::ps::Socket<int> ps_socket  = [&](int& v) {result1 = &v; };
	dvo::ps::Socket<int> ps_socket2 = [&](int& v) {result2 = &v; };
	dvo::ps::SimpleSelector<int, int, int> tester(1, 2);
	tester([&](int& v)->int& { return ++v; }); //define the processing function
	tester = 2; //select the output

     //define connections
	tester(1) >> ps_socket;
	tester(2) >> ps_socket2;
    
     //send the value into the network
	
    *source >> tester.Input();
    
    //test the result
	bool test_failed = result1 == source || result2 != source;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Selector" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_ps_restrictor()
{
	//initiate the result and the value variables
    int *source1 = new int(1), *source2 = new int(2), *result = nullptr;  

    //initiate processing elements
	dvo::ps::Socket<int> ps_socket = [&](int& fwd) {result = &fwd; };
    dvo::ps::SimpleRestrictor<int, int, int> tester(1, 2);
	tester([&](int& v)->int& { return ++v; });
	tester = 1; //restrict inputs

    //define connections
	tester.Output() >> ps_socket;
    
    //send the value into the network
	*source1 >> tester(1);
    *source2 >> tester(2);
	
    //test the result
    bool test_failed = result != source1;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Restrictor" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}

void test_ps_separator()
{
	//initiate the result and the value variables
	int *source = new int(1), *result = nullptr;

    //initiate processing elements
	dvo::ps::Socket<int> ps_socket = [&](int& fwd) { result = &fwd; };
    dvo::ps::Separator<int> tester;
	
    //define connections
	tester.Output() >> ps_socket;
    
    //send the value into the network
	*source >> tester.Input();
	
    //test the result
	tester.Wait();
    bool test_failed = result != source;
	std::cout << std::setw(9) << "push-flow" << std::setw(12) << "Separator" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


//-----------------------------------------------------------------------------------------------


void test_pl_receiver()
{
	//initiate the result and the value variables
	int source = 1, *result;
	
	//initiate processing elements
    dvo::pl::Socket<int> pl_socket = [&]()->int& { return source; };
	dvo::pl::SimpleReceiver<int> tester = [&](int& value) {result = &++value; }; 
	
	//send the value into the network
	pl_socket >>= tester.Input(); 

	//test the result
    tester();
	bool test_failed = result != &source;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Receiver" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_transmitter()
{
	//initiate the result and the value variables
	int source = 1, *result = nullptr, *check = &source;

	//initiate processing elements
	dvo::pl::SimpleTransmitter<int> tester = [&]()->int& {return source; };
	
	//define connections
	tester.Output() << result;

	//test the result
	bool test_failed = result != &source;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Transmitter" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}

void test_pl_transceiver()
{
	//initiate the result and the value variables
	int source = 1, *result = nullptr;

	//initiate processing elements
	dvo::pl::Socket<int> pl_socket = [&]()->int& { return source; };
	dvo::pl::SimpleTransceiver<int, int> tester = [&](int& value)->int& { return ++value; };
	
	//define connections
	pl_socket << tester.Input();

	//request the value from the network
	tester.Output() << result;

	//test the result
	bool test_failed = result != &source;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Transceiver" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_joiner()
{
    //initiate the result and the value variables
	int value = 2;
	int *r1, *r2;
    
    //initiate processing elements
    dvo::pl::Socket<int> pl_socket = [&]()->int& { return value; };
	dvo::pl::SimpleJoiner<int, int, int> tester(1, 2);
	tester = [&](int k, int& v)->int& { return v; };

     //define connections
	pl_socket << tester.Input();
    
    //request the value from the network
	auto signal1 = std::async(std::launch::async, [&](){tester.Outputs(1) << r1;});
    auto signal2 = std::async(std::launch::async, [&](){tester.Outputs(2) << r2;});
    
    //test the result
    signal1.wait();signal2.wait();
    bool test_failed = r1 != r2;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Joiner" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_detacher()
{
    //initiate the result and the value variables
    int source2 = 2, source1 = 1;
    int *result;

    //initiate processing elements
	dvo::pl::Socket<int> pl_socket = [&]()->int& { return source1; };
    dvo::pl::Socket<int> pl_socket2 = [&]()->int& { return source2; };
    dvo::pl::SimpleDetacher<int, int, int> tester(1, 2);
	tester = [&](std::map<int, int*>& map)->int& { return *map[1] = *map[1] + *map[2]; };
	
    //define connections
    pl_socket << tester(1);
    pl_socket2 << tester(2);
    
    //send the value into the network
	tester.Output() << result;
	
    //test the result
    bool test_failed = result != &source1;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Detacher" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_selector()
{
	 //initiate the result and the value variables
	int *result;
	int src1 = 1, src2 = 2;
    
    //initiate processing elements
	dvo::pl::Socket<int> pl_socket = [&]()->int&  { return src1; };
    dvo::pl::Socket<int> pl_socket2 = [&]()->int& { return src2; };
	dvo::pl::SimpleSelector<int, int, int> tester(1, 2);
	tester = [&](int& v)->int& { return ++v; };

     //define connections
	pl_socket << tester(1);
    pl_socket2 << tester(2);

     //send the value into the network
	tester = 2;
    tester.Output() << result;

    //test the result
    bool test_failed = result != &src2;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Selector" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_restrictor()
{
	//initiate the result and the value variables
    int *dst1, *dst2;
	int value = 1, null = 0;

    //initiate processing elements
	dvo::pl::Socket<int> pl_socket = [&]()->int& { return value; };
    dvo::pl::SimpleRestrictor<int, int, int> tester(1, 2);
	tester = [&](int k, int& v)->int& { return ++v; };
    tester = 2;
    tester.SetDefault(&null);

    //define connections
	pl_socket << tester.Input();
    
    //send the value into the network
	tester(1) << dst1;
    tester(2) << dst2;  
	
    //test the result
    bool test_failed = dst1 != &null || dst2 != &value;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Restrictor" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_separator()
{
	//initiate the result and the value variables
	int value = 2;
    int *input = &value, *output;

    //initiate processing elements
    dvo::pl::Socket<int> pl_socket = [&]()->int& { return value; };
    dvo::pl::Separator<int> tester;
	
    //define connections
	pl_socket << tester.Input();
    
    //request a value from the network
    int& result = tester.Output().Request();
	output = &result;
    //test the result
    bool test_failed = result != 2 || input != output;
	std::cout << std::setw(9) << "pull-flow" << std::setw(12) << "Separator" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_accumulator()
{
	//initiate the result and the value variables
    int *result, source = 2;  

    //initiate processing elements
    dvo::as::Accumulator<int> tester;
        
    //send the value into the network
    source >> tester.Input();
    tester.Output() << result;
	
    //test the result
    bool test_failed = result != &source;
    std::cout << std::setw(12) << "hybrid-flow" << std::setw(15) << "Accumulator" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_relayer()
{
	//initiate the result and the value variables
    int result = 0;  
	int value = 2;

    //initiate processing elements
    dvo::ps::Socket<int> ps_socket = [&](int& v) { result = v; };
    dvo::pl::Socket<int> pl_socket = [&]()->int& { return value; };
    dvo::as::Relayer<int> tester;
	
    //define connections
    pl_socket << tester.Input();
    tester.Output() >> ps_socket;
        
    //send the value into the network
    tester.Process();
	
    //test the result
    bool test_failed = result != 2;
	std::cout << std::setw(12) << "hybrid-flow " << std::setw(15) << "Relayer" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_provider()
{
	//initiate the result and the value variables

    //initiate processing elements
    dvo::as::Provider<int> tester;
	
    //define connections
        
    //send the value into the network
    int& value = tester.Output().Request();
    value >> tester.Input();
    value = 2;
    int& result = tester.Output().Request();
	
    //test the result
    bool test_failed = value != result || &value != &result;
    std::cout << std::setw(12) << "adapters " << std::setw(15) << "Provider" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}


void test_pl_ref_transceiver()
{
	//initiate the result and the value variables
	int value = 2;

    //initiate processing elements
    dvo::pl::Socket<int> pl_socket = [&]()->int& { return value; };
    dvo::as::pl_ref_transceiver<int> tester;
	
    //define connections
	pl_socket << tester.Input();
    
    //request a value from the network
    int& result = tester.Output().Request();
	
    //test the result
    bool test_failed = result != 2 || &result == &value;
	std::cout << std::setw(12) << "adapters " << std::setw(15) << "pl_ref_separator" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}

void test_ps_ref_transceiver()
{
	//initiate the result and the value variables
	int reference = 2;
    int* value = &reference;
    int* result = &reference;

    //initiate processing elements
    dvo::ps::Socket<int> ps_socket = [&](int& v) { result = &v; };
    dvo::as::ps_ref_transceiver<int> tester;
	
    //define connections
	tester.Output() >> &ps_socket;
    
    //send the value to the network
    *value >> tester.Input();
	
    //test the result
    bool test_failed = *result != 2 || result == value;
	std::cout << std::setw(12) << "adapters " << std::setw(18) << "ps_ref_separator" << std::setw(5) << (test_failed ? "NOK" : "OK") << std::endl;
}

